package io.kiki.sba.registry.common.model.slot;


import io.kiki.sba.registry.common.model.PublisherDigestUtil;
import io.kiki.sba.registry.common.model.PublisherUtils;
import io.kiki.sba.registry.common.model.RegisterVersion;
import io.kiki.sba.registry.common.model.dataserver.DatumDigest;
import io.kiki.sba.registry.common.model.dataserver.DatumSummary;
import io.kiki.sba.registry.common.model.slot.filter.SyncAcceptorRequest;
import io.kiki.sba.registry.common.model.slot.filter.SyncSlotAcceptorManager;
import io.kiki.sba.registry.common.model.store.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;


public final class DataSlotDiffUtils {
    private static final Logger logger = LoggerFactory.getLogger(DataSlotDiffUtils.class);

    private DataSlotDiffUtils() {
    }

    public static DataSlotDiffDigestResult diffDigestResult(Map<String, DatumDigest> targetDigestMap, Map<String, Map<String, Publisher>> sourcePublishers, SyncSlotAcceptorManager syncSlotAcceptorManager) {
        Map<String, DatumSummary> sourceSummaryMap = PublisherUtils.getDatumSummary(sourcePublishers, syncSlotAcceptorManager);
        Map<String, DatumDigest> sourceDigestMap = PublisherDigestUtil.digest(sourceSummaryMap);
        return diffDigest(targetDigestMap, sourceDigestMap);
    }

    public static DataSlotDiffDigestResult diffDigest(Map<String, DatumDigest> oldDigestMap, Map<String, DatumDigest> newDigestMap) {
        List<String> adds = new ArrayList<>();
        List<String> updates = new ArrayList<>();
        for (Map.Entry<String, DatumDigest> newEntry : newDigestMap.entrySet()) {
            final String newDataInfoId = newEntry.getKey();
            DatumDigest oldDigest = oldDigestMap.get(newDataInfoId);
            if (oldDigest == null) {
                adds.add(newDataInfoId);
                continue;
            }
            if (!oldDigest.equals(newEntry.getValue())) {
                updates.add(newDataInfoId);
            }
        }

        // find the removed dataInfoIds
        List<String> removes = new ArrayList<>();
        for (String dataInfoId : oldDigestMap.keySet()) {
            if (!newDigestMap.containsKey(dataInfoId)) {
                removes.add(dataInfoId);
            }
        }
        DataSlotDiffDigestResult result = new DataSlotDiffDigestResult(adds, updates, removes);
        return result;
    }

    public static DataSlotDiffPublisherResult diffPublishersResult(Map<String, Map<String, Publisher>> oldPublishers, List<DatumSummary> newDatumSummaries, int publisherMaxNum, SyncSlotAcceptorManager syncSlotAcceptorManager) {
        Map<String, List<Publisher>> update = new HashMap<>(newDatumSummaries.size());
        Map<String, List<String>> removed = new HashMap<>();

        int publisherCount = 0;
        int checkRound = 0;
        for (DatumSummary newDatumSummary : newDatumSummaries) {
            checkRound++;
            final String dataInfoId = newDatumSummary.getDataInfoId();
            Map<String, Publisher> oldRegisterIdToPublisherMap = oldPublishers.get(dataInfoId);
            if (oldRegisterIdToPublisherMap == null) {
                // the dataInfoId has removed, do not handle it, diffDataInfoIds will handle it
                continue;
            }
            Set<String> registerIdSet = newDatumSummary.getRegisterIdToRegisterVersionMap().keySet();
            for (String registerId : registerIdSet) {
                if (!oldRegisterIdToPublisherMap.containsKey(registerId)) {
                    List<String> list = removed.computeIfAbsent(dataInfoId, k -> new ArrayList<>());
                    list.add(registerId);
                }
            }
            List<Publisher> publisherList = new ArrayList<>();
            Map<String, RegisterVersion> newRegisterIdToRegisterVersionMap = newDatumSummary.getRegisterIdToRegisterVersionMap();
            for (Map.Entry<String, Publisher> oldEntry : oldRegisterIdToPublisherMap.entrySet()) {

                // filter publishers
                if (!syncSlotAcceptorManager.accept(SyncAcceptorRequest.buildRequest(dataInfoId, oldEntry.getValue().getPublishSource()))) {
                    continue;
                }
                final String registerId = oldEntry.getKey();
                if (!newRegisterIdToRegisterVersionMap.containsKey(registerId)) {
                    publisherList.add(oldEntry.getValue());
                    continue;
                }
                // compare version
                if (oldEntry.getValue().registerVersion().equals(newRegisterIdToRegisterVersionMap.get(registerId))) {
                    // the same
                    continue;
                }
                publisherList.add(oldEntry.getValue());
            }
            if (!publisherList.isEmpty()) {
                publisherCount += publisherList.size();
                update.put(dataInfoId, publisherList);
            }
            if (publisherCount >= publisherMaxNum) {
                // too many publishers, mark has remain
                break;
            }
        }
        // the iter has break
        final boolean hasRemian = checkRound != newDatumSummaries.size();
        DataSlotDiffPublisherResult result = new DataSlotDiffPublisherResult(hasRemian, update, removed);
        return result;
    }

    public static void logDiffResult(String requestDataCenter, DataSlotDiffPublisherResult result, int slotId) {
        if (!result.isEmpty()) {

            logger.info("DiffPublisher, requestDataCenter={}, slotId={}, remain={}, update={}/{}, remove={}/{}, removes={}", requestDataCenter, slotId, result.isHasRemain(), result.getUpdatedPublishers().size(), result.getUpdatedPublishersCount(), result.getRemovedPublishers().size(), result.getRemovedPublishersCount(), result.getRemovedPublishers().keySet());
        }
    }

    public static void logDiffResult(String requestDataCenter, DataSlotDiffDigestResult result, int slotId) {
        if (!result.isEmpty()) {
            logger.info("DiffDigest, requestDataCenter={}, slotId={}, update={}, add={}, remove={}, adds={}, removes={}", requestDataCenter, slotId, result.getUpdatedDataInfoIds().size(), result.getAddedDataInfoIds().size(), result.getRemovedDataInfoIds().size(), result.getAddedDataInfoIds(), result.getRemovedDataInfoIds());
        }
    }
}
